﻿/*
  +----------------------------------------------------------------------+
  | PHP Version 5                                                        |
  +----------------------------------------------------------------------+
  | Copyright (c) 1997-2012 The PHP Group                                |
  +----------------------------------------------------------------------+
  | This source file is subject to version 3.01 of the PHP license,      |
  | that is bundled with this package in the file LICENSE, and is        |
  | available through the world-wide-web at the following url:           |
  | http://www.php.net/license/3_01.txt                                  |
  | If you did not receive a copy of the PHP license and are unable to   |
  | obtain it through the world-wide-web, please send a note to          |
  | license@php.net so we can mail you a copy immediately.               |
  +----------------------------------------------------------------------+
  | Author:                                                              |
  +----------------------------------------------------------------------+
*/

/* $Id$ */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "php.h"
#include "php_ini.h"
#include "standard/php_string.h"
#include "standard/php_var.h"
#include "standard/php_smart_str.h"
#include "standard/basic_functions.h"
#include "ext/standard/info.h"
#include "php_memcache.h"
#include "zend_API.h"
#include "php_slowlog.h"
#include <sys/time.h>

//#define DEBUG

typedef struct _slowlogEntry
{	
	//自增序号
	long id;

	//当前请求的GET
	zval *GET;

	//当前请求的POST
	zval *POST;

	//当前请求的COOKIE
	zval *COOKIE;

	//当前执行的PHP文件名
	zval *filename;

	//执行时间 1s=1000000
	char duration[100];

}slowlogEntry;

/* If you declare any globals in php_slowlog.h uncomment this: */
ZEND_DECLARE_MODULE_GLOBALS(slowlog)


/* True global resources - no need for thread safety here */
static int le_slowlog;

static long ustime(void) {
	// struct timeval{
	// 	long int tv_sec;  // 秒数
	// 	long int tv_usec; // 微秒数
	// }
    struct timeval tv;
    long ust;

    //获得当前精确时间（UNIX到现在的时间）
    gettimeofday(&tv, NULL);
    ust = ((long)tv.tv_sec)*1000000;
    ust += tv.tv_usec;
    return ust;
}

/* {{{ slowlog_functions[]
 *
 * Every user visible function must have an entry in slowlog_functions[].
 */
const zend_function_entry slowlog_functions[] = {
	PHP_FE_END	/* Must be the last line in slowlog_functions[] */
};
/* }}} */

/* {{{ slowlog_module_entry
 */
zend_module_entry slowlog_module_entry = {
#if ZEND_MODULE_API_NO >= 20010901
	STANDARD_MODULE_HEADER,
#endif
	"slowlog",
	slowlog_functions,
	PHP_MINIT(slowlog),
	PHP_MSHUTDOWN(slowlog),
	PHP_RINIT(slowlog),		/* Replace with NULL if there's nothing to do at request start */
	PHP_RSHUTDOWN(slowlog),	/* Replace with NULL if there's nothing to do at request end */
	PHP_MINFO(slowlog),
#if ZEND_MODULE_API_NO >= 20010901
	"0.1", /* Replace with version number for your extension */
#endif
	STANDARD_MODULE_PROPERTIES
};
/* }}} */

#ifdef COMPILE_DL_SLOWLOG
ZEND_GET_MODULE(slowlog)
#endif

/* {{{ PHP_INI
 */
/* Remove comments and fill if you need to have entries in php.ini */
PHP_INI_BEGIN()
    STD_PHP_INI_ENTRY("slowlog.slow_maxtime",      "0", PHP_INI_ALL, OnUpdateLong, slow_maxtime, zend_slowlog_globals, slowlog_globals)
    STD_PHP_INI_ENTRY("slowlog.mem_host",      "localhost", PHP_INI_ALL, OnUpdateString, mem_host, zend_slowlog_globals, slowlog_globals)
    STD_PHP_INI_ENTRY("slowlog.mem_port",      "11211", PHP_INI_ALL, OnUpdateLong, mem_port, zend_slowlog_globals, slowlog_globals)
PHP_INI_END()

/* }}} */

/* {{{ php_slowlog_init_globals
 */
/* Uncomment this function if you have INI entries */
static void php_slowlog_init_globals(zend_slowlog_globals *slowlog_globals)
{
	slowlog_globals->slow_maxtime = 0;
}

/* }}} */

/* {{{ PHP_MINIT_FUNCTION
 */
PHP_MINIT_FUNCTION(slowlog)
{
	/* If you have INI entries, uncomment these lines */
	REGISTER_INI_ENTRIES();
	
	return SUCCESS;
}
/* }}} */

/* {{{ PHP_MSHUTDOWN_FUNCTION
 */
PHP_MSHUTDOWN_FUNCTION(slowlog)
{
	/* uncomment this line if you have INI entries */
	UNREGISTER_INI_ENTRIES();
	
	return SUCCESS;
}
/* }}} */

/* Remove if there's nothing to do at request start */
/* {{{ PHP_RINIT_FUNCTION
 */
PHP_RINIT_FUNCTION(slowlog)
{
	SLOWLOG_G(start) = ustime();
	return SUCCESS;
}
/* }}} */


static zval * global_query(uint type, char *name, uint len TSRMLS_DC){
	zval **globals, **ret;;

	switch(type){
		case TRACK_VARS_POST:
			(void) zend_hash_find(&EG(symbol_table), ZEND_STRS("_POST"), (void **)&globals);
			break;
		case TRACK_VARS_GET:
			(void) zend_hash_find(&EG(symbol_table), ZEND_STRS("_GET"), (void **)&globals);
			break;
		case TRACK_VARS_COOKIE:
			globals = &PG(http_globals)[type];
			break;
		case TRACK_VARS_SERVER:
			zend_is_auto_global(ZEND_STRL("_SERVER") TSRMLS_CC);
			globals = &PG(http_globals)[type];
			break;
		default :
			break;
	}

	if (!globals || !(*globals)) {
		zval *empty;
		MAKE_STD_ZVAL(empty);
		ZVAL_NULL(empty);
		return empty;
	}

	//返回整个数组
	if(!len){
		Z_ADDREF_P(*globals);
		return *globals;
	}

	//返回数组中的键值
	if(zend_hash_find(Z_ARRVAL_PP(globals), name, len + 1, (void **)&ret) == FAILURE){
		zval *empty;
		MAKE_STD_ZVAL(empty);
		ZVAL_NULL(empty);
		return empty;
	}

	Z_ADDREF_P(*ret);


	return *ret;
}

static char* slowlogEntry_to_string(slowlogEntry *log){
	php_serialize_data_t var_hash;
	zval *arr;
	smart_str buf = {0};

	MAKE_STD_ZVAL(arr);
	array_init(arr);

	add_assoc_zval(arr, "GET", log->GET);
	add_assoc_zval(arr, "POST", log->POST);
	add_assoc_zval(arr, "COOKIE", log->COOKIE);
	add_assoc_long(arr, "id", log->id);
	add_assoc_stringl(arr, "duration", log->duration, strlen(log->duration), 1);
	add_assoc_zval(arr, "filename", log->filename);

	Z_ADDREF_P(log->POST);
	Z_ADDREF_P(log->GET);
	Z_ADDREF_P(log->COOKIE);
	Z_ADDREF_P(log->filename);

	PHP_VAR_SERIALIZE_INIT(var_hash);
	php_var_serialize(&buf, &arr, &var_hash TSRMLS_CC);
	PHP_VAR_SERIALIZE_DESTROY(var_hash);

	if(buf.c){
		return buf.c;
	}else{
		return NULL;
	}
}

/* Remove if there's nothing to do at request end */
/* {{{ PHP_RSHUTDOWN_FUNCTION
 */
PHP_RSHUTDOWN_FUNCTION(slowlog)
{
	zend_function *func;
	long end = ustime(), duration;

	duration = end - SLOWLOG_G(start);

	if(duration > SLOWLOG_G(slow_maxtime)){
		zval *return_value;
		mmc_pool_t *pool;
		char *value, slowlog_idx[100];
		mmc_t *mmc;
		int errnum;
		char *error_string;

		MAKE_STD_ZVAL(return_value);
		mmc = mmc_server_new(SLOWLOG_G(mem_host), strlen(SLOWLOG_G(mem_host)), SLOWLOG_G(mem_port), 0, 1, MMC_DEFAULT_RETRY TSRMLS_CC);
		mmc->timeout = 1;
		mmc->connect_timeoutms = 1000;

		if(mmc_open(mmc, 1, &error_string, &errnum TSRMLS_CC)){
			slowlogEntry current_log;
			char new_slowkey[100];
			char *strval;

			//获取$_SERVER['PHP_SELF']
			current_log.filename = global_query(TRACK_VARS_SERVER, "PHP_SELF", sizeof("PHP_SELF") - 1);
			//获取$_COOKIE
			current_log.COOKIE   = global_query(TRACK_VARS_COOKIE, NULL, 0);
			//获取$_GET
			current_log.GET   = global_query(TRACK_VARS_GET, NULL, 0);
			//获取$_POST
			current_log.POST   = global_query(TRACK_VARS_POST, NULL, 0);

			pool = mmc_pool_new(TSRMLS_C);
			mmc_pool_add(pool, mmc, 1);

			sprintf(current_log.duration, "%ld", duration);

			mmc_exec_retrieval_cmd(pool, "slowlog_idx", 11, &return_value, NULL TSRMLS_CC);

			convert_to_long(return_value);
			current_log.id = Z_LVAL_P(return_value)+1;
			sprintf(new_slowkey, "slowlog_%ld", current_log.id);
			sprintf(slowlog_idx, "%ld", current_log.id);

			strval = slowlogEntry_to_string(&current_log);

			//将slowlogEntry序列化后存储到memcache
			mmc_pool_store(
					pool, "set", 3, new_slowkey, strlen(new_slowkey), 0, 0, strval, strlen(strval) TSRMLS_CC);

			//设置自增号
			mmc_pool_store(
					pool, "set", 3, "slowlog_idx", 11, 0, 0, 
					slowlog_idx, strlen(slowlog_idx) TSRMLS_CC);
	#ifdef DEBUG
			php_printf("insert slowlog, key : %s\n", new_slowkey);
	#endif
			
		}

	}

	return SUCCESS;
}
/* }}} */

/* {{{ PHP_MINFO_FUNCTION
 */
PHP_MINFO_FUNCTION(slowlog)
{
	php_info_print_table_start();
	php_info_print_table_header(2, "slowlog support", "enabled");
	php_info_print_table_end();

	/* Remove comments if you have entries in php.ini */
	DISPLAY_INI_ENTRIES();
}
/* }}} */


/*
 * Local variables:
 * tab-width: 4
 * c-basic-offset: 4
 * End:
 * vim600: noet sw=4 ts=4 fdm=marker
 * vim<600: noet sw=4 ts=4
 */
